﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Diagnostics;

using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;
using Microsoft.SharePoint.Security;

namespace Mystery.SharePoint
{
	/// <summary>
	/// Base class  for diagnostics services.
	/// </summary>
	/// <typeparam name="T"></typeparam>
	public abstract class DiagnosticsService<T> : SPDiagnosticsServiceBase
	{
		/// <summary>
		/// A category used to report errors in this diagnostic service configuration
		/// </summary>
		private SPDiagnosticsCategory DiagnosticsErrorCategory = 
			new SPDiagnosticsCategory("DiagnosticsError", TraceSeverity.Medium, EventSeverity.Warning);

		/// <summary>
		/// Provides the name of the diagnostics area
		/// </summary>
		public virtual string DiagnosticsAreaName { get { return "DiagnosticsService::DiagnosticsAreaName has not been specified."; } }
		/// <summary>
		/// Default constructor
		/// </summary>
		protected DiagnosticsService()
		{
		}
		/// <summary>
		/// Specialized constructor for the service
		/// </summary>
		/// <param name="name"></param>
		/// <param name="farm"></param>
		protected DiagnosticsService(string name, SPFarm farm)
			: base(name, farm)
		{
		}
		/// <summary>
		/// Provides all the areas available in this service
		/// </summary>
		/// <returns></returns>
		protected override IEnumerable<SPDiagnosticsArea> ProvideAreas()
		{
			List<SPDiagnosticsCategory> categories = new List<SPDiagnosticsCategory>();

			foreach (string catName in Enum.GetNames(typeof(T)))
			{
				uint catId = (uint)(int)Enum.Parse(typeof(T), catName);
				categories.Add(new SPDiagnosticsCategory(catName, TraceSeverity.Medium, EventSeverity.Error, 0, catId));
			}

			yield return new SPDiagnosticsArea(DiagnosticsAreaName, categories);
		}

        public void AddInitializeMessage()
        {
            SPDiagnosticsCategory category = null;

            foreach (SPDiagnosticsCategory c in Areas[DiagnosticsAreaName].Categories)
            {
                category = c;
                break;
            }

            if (category != null)
            {
                WriteTrace(0, category, TraceSeverity.Verbose, "Initializing diagnostics service: {0}", this.DiagnosticsAreaName);			
            }
            else
            {
                TraceDiagnosticsError("Could not locate a category.");
            }
        }

		/// <summary>
		/// Adds an information entry to the system
		/// </summary>
		/// <param name="category">The type of category</param>
		/// <param name="output">A composite format string</param>
		/// <param name="data">An object array containing zero or more objects to format</param>
		public void AddInformation(T category, string output, params object[] data)
		{
			WriteEvent(category, EventSeverity.Information, TraceSeverity.Verbose, output, data);
		}
		/// <summary>
		/// Adds a warning entry to the system
		/// </summary>
		/// <param name="category">The type of category</param>
		/// <param name="output">A composite format string</param>
		/// <param name="data">An object array containing zero or more objects to format</param>
		public void AddWarning(T category, string output, params object[] data)
		{
			WriteEvent(category, EventSeverity.Warning, TraceSeverity.Medium, output, data);
		}
		/// <summary>
		/// Adds an error entry to the system
		/// </summary>
		/// <param name="category">The type of category</param>
		/// <param name="output">A composite format string</param>
		/// <param name="data">An object array containing zero or more objects to format</param>
		public void AddError(T category, string output, params object[] data)
		{
			WriteEvent(category, EventSeverity.Error, TraceSeverity.High, output, data);
		}
		/// <summary>
		/// Adds an exception entry to the system
		/// </summary>
		/// <param name="category">The type of category</param>
		/// <param name="ex">An exception object whos data will be logged</param>
		/// <param name="data">An object array containing zero or more objects to format</param>
		public void AddException(T category, Exception ex)
		{
			AddException(category, ex, null);
		}
		/// <summary>
		/// Adds an exception entry to the system
		/// </summary>
		/// <param name="category">The type of category</param>
		/// <param name="ex">An exception object whos data will be logged</param>
		/// <param name="output">A composite format string</param>
		/// <param name="data">An object array containing zero or more objects to format</param>
		public void AddException(T category, Exception ex, string output, params object[] data)
		{
			if (ex == null) throw new ArgumentNullException("ex");

			StringBuilder sb = new StringBuilder();

			if (string.IsNullOrEmpty(output) == false)
			{
				sb.AppendFormat(null, output, data);
			}
			sb.AppendFormat(null, "{0}\t", ex.Message);
			sb.AppendFormat(null, "{0}\t", ex.StackTrace);

			SPException spe = ex as SPException;
			if (spe != null)
			{
				sb.AppendFormat(null, "{0}\t", spe.NativeErrorMessage);
				sb.AppendFormat(null, "{0}\t", spe.NativeStackTrace);
			}
			WriteEvent(category, EventSeverity.ErrorCritical, TraceSeverity.Unexpected, output, data);
		}
		/// <summary>
		/// Retrieves the category with the specified id
		/// </summary>
		/// <param name="id">ID of category</param>
		/// <returns></returns>
		public SPDiagnosticsCategory this[T id]
		{
			get
			{
				return Areas[DiagnosticsAreaName].Categories[id.ToString()];
			}
		}

		/// <summary>
		/// Registers all the areas as event sources
		/// </summary>
		public void RegisterEventSources()
		{
			foreach(SPServer server in SPFarm.Local.Servers)
			{
				if (server.Role != SPServerRole.Invalid)
				{
					foreach (SPDiagnosticsArea area in this.Areas)
					{
						if (!EventLog.SourceExists(area.Name, server.Address))
						{
							try
							{
								EventSourceCreationData createData = new EventSourceCreationData(area.Name, "SharePoint Event Log");
								createData.MachineName = server.Address;

								EventLog.CreateEventSource(createData);
							}
							catch (Exception ex)
							{
								TraceDiagnosticsError("Error creating event source '{0}' on machine '{1}': {2}",
									area.Name, server.Address, ex.Message);
								throw;
							}
							
						}
					}
				}
			}
		}
        private void TraceDiagnosticsError(string output, params object[] data)
        {
            this.WriteTrace(0, this.DiagnosticsErrorCategory, TraceSeverity.Unexpected, output, data);
        }

        private void WriteEvent(T category, EventSeverity eventSeverity, TraceSeverity traceSeverity, string output, params object[] data)
        {
            SPDiagnosticsCategory c = this[category];
            if (c != null)
            {
                try
                {
                    //this.WriteEvent(0, c, eventSeverity, output, data);
                    this.WriteTrace(0, c, traceSeverity, output, data);
                }
                catch (Exception)
                {
                    this.WriteTrace(0, c, TraceSeverity.Unexpected, "Failed to write event: {0}", string.Format(null, output, data));
                }
            }
            else
            {
                TraceDiagnosticsError("Could not find category '{0}'", category);
            }
        }
	}
}
